/**
 * \file somkfs.c
 *
 * \brief The SOFS08 formating tool
 *
 * \author Put your name here
 * \author Put your name here
 * \author Put your name here
 * \author Put your name here
 * \author Put your name here
 *
 * \date ...
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/stat.h>

#include "sofs_const.h"
#include "sofs_rawdisc.h"
#include "sofs_superblock.h"
#include "sofs_inode.h"
#include "sofs_direntry.h"

/* Allusion to internal functions */
static void printUsage (char *cmd_name);
static void printError (int errcode, char *cmd_name);

/* Macro definition */
#define ABORT_ON_ERROR(err) \
    {\
        if (!quiet) printError(err, argv[0]);\
        return EXIT_FAILURE;\
    }

/* The main function */
int main(int argc, char *argv[])
{
    char *name = "SOFS08";             /* volume name */
    int quiet = 0;                     /* quiet mode ? */
    int zero = 0;                      /* zero mode ? */
    int ret;

    /*variaveis auxiliares*/

    /* variavel do superBloco */
    SOSuperBlock superblock;
    
    /* array que contem o bloco de nos-i */
    SOInode inode;
    
    /* vai conter de forma temporario a informacao de cada entrada de directorio */
    SODirEntry dirEntry;
    
    /* guarda um bloco temporariamente */
    char block[BLOCK_SIZE];

    /* declaracao dos contadores */
    u_int32_t i,j,k;



    /* number of inodes */
    u_int32_t itotal = 0;  // 0 means generate automatically
    
    /* process command line options */
    int opt;
    do
    {
        switch ((opt = getopt(argc, argv, "n:i:l:L:qzh")))
        {
            case 'n':
                name = optarg;
                break;

            case 'i':
                itotal = atoi( optarg );
                break;

            case 'q':
                quiet = 1;
                break;

            case 'z':
                zero = 1;
                break;

            case 'h':
                printUsage(basename(argv[0]));
                return EXIT_SUCCESS;

            case -1:
                break;

            default:
                fprintf(stderr, "%s: Wrong option.\n", basename(argv[0]));
                printUsage(basename(argv[0]));
                return EXIT_FAILURE;
        }
    } while (opt != -1);

    /* check existence of mandatory argument: device name */
    int nmargs = argc - optind; 
    if (nmargs != 1)
    {
        fprintf(stderr, "%s: Wrong number of mandatory arguments.\n", basename(argv[0]));
        printUsage(basename(argv[0]));
        return EXIT_FAILURE;
    }
    
    char *devname = argv[optind];

    /* check device for conformity */
    struct stat st;
    if (stat(devname, &st) == -1)
    {
        perror("Fail stating disk support file");
        return EXIT_FAILURE;
    }
    if (st.st_size % BLOCK_SIZE != 0)
    {
        fprintf(stderr, "Bad size of support file\n");
        return EXIT_FAILURE;
    }

    /* determine total number of blocks */
    u_int32_t ntotal = st.st_size / BLOCK_SIZE;

    /* determine the total number of inodes, rounded up to a multiple of IPB */
    if (itotal == 0)
    {
        /* put your code here */
	itotal=ntotal/8;
    }

    if (itotal % IPB != 0)
    {
        /* put your code here */
	itotal+=IPB-(itotal%IPB);
    }

    if (!quiet) printf("\e[34mInstalling a %d-inodes SOFS08 file system in %s.\e[0m\n", 
                    itotal, argv[optind]);

    /************************************************************************/

    /* open device */
    if ((ret = soOpenDevice(argv[optind])) < 0) ABORT_ON_ERROR(-ret);

    /************************************************************************/

    /* Fill superblock */
    if (!quiet) printf("Filling superblock... "); fflush(stdout);


    /* put your code here */
    superblock.magic=0x50F5;
    superblock.version=0x2008;
    strncpy(superblock.name,name,24);
    superblock.ntotal=ntotal;
    superblock.itotal=itotal;
    superblock.ifree=itotal-1;
    superblock.itable_size=itotal/IPB;

    /* por definicao o primeiro inode encontra-se no bloco 1*/
    superblock.itable_start=1;
    superblock.ihead=1;
    superblock.itail=itotal-1;

    /* o numero de blocos para a zona de dados sera o numero total de blocos menos os blocos necessarios para a tabela de nos-i
      menos um bloco para o super bloco
    */
    superblock.dzone_size=ntotal-(superblock.itable_size)-1;

    /* ao inicio a zona de dados encontra-se toda livre */
    superblock.dzone_free=superblock.dzone_size-1;
 
    /* o primeiro bloco da zona de dados sera igual ao tamanho ocupado pelo superbloco mais o tamanho ocupado pela tabela dos nos-i */
    superblock.dzone_start=1+(superblock.itable_size);
    
    /* ao inicio a cache encontra-se vazia*/
    superblock.dzone_head.cache_cnt=0;
    superblock.dzone_head.cache_idx=0;
    superblock.dzone_head.blk_number=ntotal-1;
    superblock.dzone_head.blk_idx=0;

    /* vai ter de ser escrito no final */ 
    superblock.dzone_tail.cache_cnt=0;
    superblock.dzone_tail.cache_idx=0;

    /*the superblock is wrote only after the free block list have been filled*/
   
    if (!quiet) printf("done.\n");

    /************************************************************************/

    /* fill inode table:
     *   all inodes are free, except the one
     *   corresponding to the root directory,
     *   which by definition is inode number zero.
     */
    if (!quiet) printf("Filling inode table ... "); fflush(stdout);
    
    /* put your code here */
   
    /* informacoes relativas ao inode 0 */
    inode.mode=7|(7<<3)|(7<<6)|INODE_DIR;
    inode.owner=geteuid();
    inode.group=getgid();
    inode.size=2*sizeof(SODirEntry);
    inode.counts=1|(2<<24);
    inode.atime=time(NULL);
    inode.mtime=time(NULL);
    inode.ctime=time(NULL);
    inode.d[0]=superblock.dzone_start;
    inode.d[1]=NULL_BLOCK;
    inode.d[2]=NULL_BLOCK;
    inode.d[3]=NULL_BLOCK;
    inode.d[4]=NULL_BLOCK;
    inode.d[5]=NULL_BLOCK;
    inode.i1=NULL_BLOCK;
    inode.i2=NULL_BLOCK;
    memcpy(block,&inode,sizeof(SOInode));
    


    /* colocar todos os inodes para o bloco auxiliar. quando o bloco auxiliar esta cheio,
      guarda o bloco auxiliar em ficheiro isto ate todos os inodes estarem em ficheiro */

    inode.mode=0;


    for(i=1;i<=itotal;i++)
    {
	if((i % IPB )==0) 
	{	
		/* guardar o bloco auxiliar em ficheiro */
	  	if ((ret = soWriteRawBlock((i / IPB),block)) < 0) 
		  	ABORT_ON_ERROR(-ret);
	}
	if(i==(itotal-1))	
		/* se o inode for o ultimo o next vai apontar para NULL_INODE */
		inode.next=NULL_INODE;
	else
		/* o inode aponta para o inode seguinte */
	  	inode.next=i+1;

	if(i<itotal)
		memcpy(block+(i % IPB)*sizeof(SOInode),&inode,sizeof(SOInode));	
    }


 
    
    if (!quiet) printf("done.\n");

    /************************************************************************/

    /* Fill root data block:
     *   the first 2 entries are filled with . and .. 
     *   the other are empty.
     */
    if (!quiet) printf("Filling root data block... "); fflush(stdout);

    /* put your code here */
   
    /* a tabela de entrada do directorio / vai ter as entradas . e ..*/
    
    /* bloco com as entradas de directorio de '/'*/
    /* entrada . */
    dirEntry.inode=0;
    strcpy(dirEntry.name,".");
    memcpy(block,&dirEntry,sizeof(SODirEntry));
    /* entrada .. */
    dirEntry.inode=0;
    strcpy(dirEntry.name,"..");
    memcpy(block+sizeof(SODirEntry),&dirEntry,sizeof(SODirEntry));


    /* escrever o bloco com as entradas de directório no disco */
    if ((ret=soWriteRawBlock(superblock.dzone_start,block)) < 0)
	    ABORT_ON_ERROR(-ret);
 
    if (!quiet) printf("done.\n");

    /************************************************************************/

    /* Fill free block list:
     */
    if (!quiet) printf("Filling free block list... "); fflush(stdout);

    /* put your code here */

    u_int32_t refsBlock[RPB];

    k=superblock.dzone_start+1;

    /* percorrer todos os inodes livres e ir colocando o nº dos inodes num bloco auxiliar de referências. 
     * quando o bloco auxiliar se encontrar cheio é escrito para o disco */
    for(i=ntotal-1;k<i;i--)
    {
    	for(j=0; j<RPB-1;j++)
		if (k>=i)
		    break;
		else 
                   refsBlock[j]=k++;
        if(i-1>k)
	    refsBlock[RPB-1]=i-1;
	else
            refsBlock[RPB-1]=NULL_BLOCK;
        
	/* escrever o bloco com as referências no disco */
	if((ret=soWriteRawBlock(i,refsBlock))<0)
	    ABORT_ON_ERROR(-ret);
    }
	
    superblock.dzone_tail.blk_number=i+1;
    superblock.dzone_tail.blk_idx=j;

    /* efectua a gravação do superbloco propriamente dita */
    if ((ret = soWriteRawBlock(0,&superblock)) < 0) 
	    ABORT_ON_ERROR(-ret);
 
    if (!quiet) printf("done.\n");

    /************************************************************************/

    /* Zero fill the remaining blocks if full formating was required */
    if (zero)
    {
        if (!quiet) printf("Clearing free data blocks... "); fflush(stdout);

        /* put your code here */
    
	/* colocar o bloco auxiliar a zeros */
	memset(block,0,BLOCK_SIZE);

	/* percorrer os blocos livres do disco e escrever neles o bloco auxiliar que contem apenas 0s */
	for(i=superblock.dzone_start+1; i<superblock.dzone_tail.blk_number; i++)
	{	if ((ret = soWriteRawBlock(i,&block)) < 0) 
			ABORT_ON_ERROR(-ret);
	}

        if (!quiet) printf("done.\n");
    }
    
    /************************************************************************/

    /* that's all */
    if (!quiet) printf("Formating concluded\n");
    return EXIT_SUCCESS;

} /* end of main */

/************************************************************************/
/************************************************************************/
/************************************************************************/

/*
 * Help message
 */
void printUsage(char *cmd_name)
{
    printf("Sinopsis: %s [OPTIONS] supp-file\n"
        "\tOPTIONS:\n"
        "\t -n volume-name   --- set volume name (default: \"SOFS08\"\n"
        "\t -i               --- set number of inodes (default: N/8, where N is the number of blocks)\n"
        "\t -z               --- zero mode (default: not zero)\n"
        "\t -q               --- quiet mode (default: not quiet)\n"
        "\t -h               --- this help\n", cmd_name);
}


/*
 * Error processing
 */
void printError(int errcode, char *cmd_name)
{
    fprintf(stderr, "%s: error #%d: %s\n", cmd_name, errcode, strerror(errcode));
}

